
/*
 * Vivien2
 *
 * Copyright (c) 2009 Alexei Samsonov (aka Lancelot).
 * E-mail: alexey.v.samsonov@gmail.com; 
 *
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */


/**
 * Authors: Alexei Samsonov (aka Lancelot), alexey.v.samsonov@gmail.com
 */

/**
 * Date: 
 */


/**
 * Summary:
 *
 * Требования к конфигурационному файлу:
 * 1. Строка после символа «#» и пробелы перед ним игнорируется. 
 *    Последовательность пробелов и табуляций интерпретируется, как один пробел.
 * 2. Завершающие пробелы и символы табуляции игнорируются.
 * 3. Каждая строка состоит из имени параметра и его значения, 
 *    разделённых пробелами.
 * 4. Если значение параметра содержит пробелы, то его необходимо взять 
 *    в двойные кавычки.
 */


module config;


import std.stdio;
import std.file;
import std.path;
import std.regexp;
import std.string;
import std.base64;
import std.conv;




class Config
{
	// Массив параметров конфигурационного файла.
	char[][char[]] values;



	// Места возможного расположения кофигурационного файла.
	// Обрабатываются последовательно, в порядке следования.
	// Последующие параметры переопределяют предыдущие.
	private char[][] _paths = ["./vivien.conf"];



	this()
	{
		foreach(char[] file; _paths)
		{
			// Заменяем тильду в имени путём к домашнему каталогу.
			file = expandTilde(file);

			if((exists(file) == 1) && (isfile(file)))
			{
				char[] content = cast(char[])read(file);

				setValues(content);
			}
		}
	}


	this(char[][] paths)
	{
		_paths = paths;
		this();
	}


	/**
	 * Возвращает строковое значение требуемого параметра конфигурационного файла.
	 * Если параметр не задан, то возвращает пустое значение "".
	 */
	char[] get(char[] param)
	{
		foreach(char[] p, char[] v; values)
		{
			if(p == param)
			{
				return v;
			}
		}

		return "";
	}



	char[][char[]] get()
	{
		return values;
	}



	private void setValues(char[] content)
	{
		RegExp reg;

		/**
		 * Примечание по reg.replace():
		 * Если в регулярном выражении используется символ конца строки $, 
		 * то он тоже заменяется. Т. е. если символ конца строки в результате
		 * должен остаться, то необходимо его добавить к заменяющему выражению.
		 */
		
		// Удаляем всё после символа «#» и пробелы перед ним.
		reg = new RegExp ("[\t ]*#.*$", "mg");
		content = reg.replace(content, "\n");

		// Удаляем пустые строки и строки с пробельными символами.
		reg = new RegExp ("^[\t ]*$", "mg");
		content = reg.replace(content, "");

		// Удаляем пробельные символы в начале строки.
		reg = new RegExp ("^[\t ]+", "mg");
		content = reg.replace(content, "");

		// Удаляем пробельные символы в конце строки.
		reg = new RegExp ("[\t ]+$", "mg");
		content = reg.replace(content, "\n");

		// Строки в двойных кавычках кодируем в Base64, чтобы экранировать пробелы в них.
		content = sub(content, "\"([^\"]*)\"", 
			delegate char[] (RegExp m) 
			{
				return "\"" ~ std.base64.encode(m.match(1)) ~ "\"";
			},
			"mg");

		// Последовательность пробелов и табуляций, как один пробел.
		reg = new RegExp ("[\t ]+", "mg");
		content = reg.replace(content, " ");

		// Удаляем параметры не имеющие значений.
		reg = new RegExp ("^[^ ]+$", "mg");
		content = reg.replace(content, "");

		// Удаляем параметры с пустым значением "".
		reg = new RegExp ("^[^ ]+ \"\"$", "mg");
		content = reg.replace(content, "");




		char[][] lines = splitlines(content);
		foreach(char[] line; lines)
		{
			char[][] param = std.string.split(line);


			// Декодируем значения в двойных кавычках.
			if (auto m = std.regexp.search(param[1], "\"([^\"]*)\""))
			{
				param[1] = std.base64.decode(m.match(1));
			}

			values[param[0]] = param[1];
		}
	}



	unittest
	{
		char[] n1 = "vivien_config_1.tmp";
		char[] n2 = "vivien_config_2.tmp";
		char[] n3 = "vivien_config_3.tmp";

		char[] v1 = "\n" ~
			"	    # Config File #1 Example.                       \n" ~
			"   	# -----------------------                       \n" ~
			"           				        	                \n" ~
			"	    FirstName  	 Ivan   	# First parameter.      \n" ~
			"       LastName  	 Petrov   	# Second parameter.     \n" ~
			"\n" ~
			"       Login  	   \"\" 	    # Wrong parameter value.\n" ~
			"	    Password                # Wrong parameter.      \n" ~
			"       About	   \"System Administrator.\"   	        \n" ~
			"                                                       \n" ~
			"\n" ~
			"\n" ~
			"\n";

		char[] v2 = "\n" ~
			"  # Config File #2 Example.          \n" ~
			"  # -----------------------          \n" ~
			"  FirstName 		Fedot             \n" ~
			"  LastName  	    Sidorov           \n" ~
			"  NickName 	    Sidor             \n" ~
			"  About	      \"Power User.\"     \n" ~
			"\n";

		char[] v3 = "" ~
			"# Config File #3 Example.\n" ~
			"# -----------------------\n" ~
			"FirstName Ipolit\n" ~
			"LastName Ivanov\n" ~
			"About \"Unix Guru.\" \n" ~
			"ID 1239087\n" ~
			"";

		write(n1, v1);
		write(n2, v2);
		write(n3, v3);

		
		char[][] paths;
		Config c;
		
		paths = [n1];
		c = new Config(paths);
		assert(c.get("FirstName") == "Ivan");
		assert(c.get("LastName")  == "Petrov");
		assert(c.get("About")     == "System Administrator.");
		assert(c.get("ID")        == "");
		assert(c.get("NickName")  == "");
		assert(c.get("Login")     == "");
		assert(c.get("Password")  == "");
		assert(c.get("NoParam")   == "");

		paths = [n1, n2];
		c = new Config(paths);
		assert(c.get("FirstName") == "Fedot");
		assert(c.get("LastName")  == "Sidorov");
		assert(c.get("About")     == "Power User.");
		assert(c.get("ID")        == "");
		assert(c.get("NickName")  == "Sidor");
		assert(c.get("Login")     == "");
		assert(c.get("Password")  == "");
		assert(c.get("NoParam")   == "");

		paths = [n1, n2, n3];
		c = new Config(paths);
		assert(c.get("FirstName") == "Ipolit");
		assert(c.get("LastName")  == "Ivanov");
		assert(c.get("About")     == "Unix Guru.");
		assert(c.get("ID")        == "1239087");
		assert(c.get("NickName")  == "Sidor");
		assert(c.get("Login")     == "");
		assert(c.get("Password")  == "");
		assert(c.get("NoParam")   == "");
	

		std.file.remove(n1);
		std.file.remove(n2);
		std.file.remove(n3);
	}
}

